﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Threading;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;

namespace Geek7Utils
{

    // 简易Http
    class SimpleHttp : IDisposable
    {
        private readonly HttpListener _listener;                        // HTTP 协议侦听器
        private readonly Thread _listenerThread;                        // 监听线程
        private readonly Thread[] _workers;                             // 工作线程组
        private readonly ManualResetEvent _stop, _ready;                // 通知停止、就绪
        private Queue<HttpListenerContext> _queue;                      // 请求队列
        //POST方法字典
        Dictionary<string, RouteInfo> _route_pool = new Dictionary<string, RouteInfo>();


        //构造 ( 最大多线程数 )
        public SimpleHttp(int maxThreads)
        {
            _workers = new Thread[maxThreads];
            _queue = new Queue<HttpListenerContext>();
            _stop = new ManualResetEvent(false);
            _ready = new ManualResetEvent(false);
            _listener = new HttpListener();
            _listenerThread = new Thread(HandleRequests);
        }

        //启动Http服务器
        public void Start(int port)
        {

            // 启动Http服务
            _listener.Prefixes.Add(String.Format("http://*:{0}/", port));
            _listener.Start();
            _listenerThread.Start();

            // 启动工作线程
            for (int i = 0; i < _workers.Length; i++)
            {
                _workers[i] = new Thread(Worker);
                _workers[i].Start();
            }
        }

        //响应
        public void Response(HttpListenerContext ctx, string res, int state = 200)
        {
            byte[] buffer = Encoding.UTF8.GetBytes(res);
            Response(ctx, buffer, state);
        }

        //响应
        public void Response(HttpListenerContext ctx, byte[] buffer, int state = 200)
        {
            //返回信息
            ctx.Response.StatusCode = state;
            ctx.Response.Headers.Add("Access-Control-Allow-Origin", "*");//跨域
            ctx.Response.ContentLength64 = buffer.Length;
            ctx.Response.OutputStream.Write(buffer, 0, buffer.Length);
            ctx.Response.OutputStream.Close();
            ctx.Response.Close();
        }

        //添加处理方法  
        public void AddRouter(string method, Action<HttpListenerContext, string, HttpMultipartParser> handler, string fileKey = "file")
        {
            _route_pool.Add(method, new RouteInfo()
            {
                _handFunc = handler,
                _fileKey = fileKey
            });
        }

        // 释放资源
        public void Dispose()
        {
            Stop();
        }

        // 停止服务
        public void Stop()
        {
            _stop.Set();
            _listenerThread.Join();
            foreach (Thread worker in _workers)
            {
                worker.Join();
            }
            _listener.Stop();
        }

        // 处理请求
        private void HandleRequests()
        {
            while (_listener.IsListening)
            {
                var context = _listener.BeginGetContext(ContextReady, null);
                if (0 == WaitHandle.WaitAny(new[] { _stop, context.AsyncWaitHandle }))
                {
                    return;
                }
            }
        }

        // 请求就绪加入队列
        private void ContextReady(IAsyncResult ar)
        {
            try
            {
                lock (_queue)
                {
                    _queue.Enqueue(_listener.EndGetContext(ar));
                    _ready.Set();
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(string.Format("[HttpServerBase::ContextReady]err:{0}", e.Message));
            }
        }

        // 处理一个任务
        private void Worker()
        {
            WaitHandle[] wait = new[] { _ready, _stop };
            while (0 == WaitHandle.WaitAny(wait))
            {
                HttpListenerContext context;
                lock (_queue)
                {
                    if (_queue.Count > 0)
                        context = _queue.Dequeue();
                    else
                    {
                        _ready.Reset();
                        continue;
                    }
                }

                try
                {
                    ProcessHttpRequest(context);
                }
                catch (Exception e)
                {
                    Console.WriteLine(string.Format("[HttpServerBase::Worker]err:{0}", e.Message));
                    Error(context); //返回前端消息 防止前端锁死
                }
            }
        }

        // 请求处理函数
        protected void ProcessHttpRequest(HttpListenerContext ctx)
        {
            var url = ctx.Request.RawUrl.ToString();

            HttpListenerRequest request = ctx.Request;
            HttpListenerResponse response = ctx.Response;

            if (request.HttpMethod == "POST")
            {
                //获取调用方法名
                int index = url.IndexOf('/', 1);

                if (index != -1)
                {
                    string method = url.Substring(1, index - 1);
                    RouteInfo router = null;

                    //获取函数指针
                    if (!_route_pool.TryGetValue(method, out router))
                    {
                        Error(ctx);
                        return;
                    }

                    //获取Body
                    var hmp = new HttpMultipartParser(request.InputStream, router._fileKey);

                    //获取参数
                    string param = index + 1 < url.Length ? url.Substring(index + 1, url.Length - 1 - index) : null;

                    //处理
                    router._handFunc(ctx, param, hmp);
                }
                else
                {
                    Error(ctx, $"post error!!! example:\n\thttp://{ctx.Request.UserHostAddress}/hello/");
                }
            }
            else
            {
                Error(ctx, "Only the POST method is supported !");
            }

        }

        //默认错误处理
        public void Error(HttpListenerContext context, string errContent = "Not Found Method!")
        {
            Response(context, errContent, 444);
        }
    }


    /// <summary>
    /// 路由信息
    /// </summary>
    internal class RouteInfo
    {
        //处理方法
        public Action<HttpListenerContext, string, HttpMultipartParser> _handFunc = null;
        //文件指定KEY
        public string _fileKey = "file";
        //TODO 密钥
    }


    /// <summary>
    /// multipart/form-data的解析器
    /// </summary>
    internal class HttpMultipartParser
    {
        /// <summary>
        /// 参数集合
        /// </summary>
        public IDictionary<string, string> Parameters = new Dictionary<string, string>();
        /// <summary>
        /// 上传文件部分参数
        /// </summary>
        public string FilePartName { get; }
        /// <summary>
        /// 是否解析成功
        /// </summary>
        public bool Success { get; private set; }
        /// <summary>
        /// 请求类型
        /// </summary>
        public string ContentType { get; private set; }
        /// <summary>
        /// 上传的文件名
        /// </summary>
        public string Filename { get; private set; }
        /// <summary>
        /// 上传的文件内容
        /// </summary>
        public byte[] FileContents { get; private set; }

        /// <summary>
        /// 解析multipart/form-data格式的文件请求，默认编码为utf8
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="filePartName"></param>
        public HttpMultipartParser(Stream stream, string filePartName)
        {
            FilePartName = filePartName;
            Parse(stream, Encoding.UTF8);
        }

        /// <summary>
        /// 解析multipart/form-data格式的字符串
        /// </summary>
        /// <param name="content"></param>
        public HttpMultipartParser(string content)
        {
            var array = Encoding.UTF8.GetBytes(content);
            var stream = new MemoryStream(array);
            Parse(stream, Encoding.UTF8);
        }

        /// <summary>
        /// 解析multipart/form-data格式的文件请求
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="encoding">编码</param>
        /// <param name="filePartName"></param>
        public HttpMultipartParser(Stream stream, Encoding encoding, string filePartName)
        {
            FilePartName = filePartName;
            Parse(stream, encoding);
        }

        private void Parse(Stream stream, Encoding encoding)
        {
            Success = false;

            var data = ToByteArray(stream);

            var content = encoding.GetString(data);

            var delimiterEndIndex = content.IndexOf("\r\n", StringComparison.Ordinal);

            if (delimiterEndIndex > -1)
            {
                var delimiter = content.Substring(0, content.IndexOf("\r\n", StringComparison.Ordinal)).Trim();

                var sections = content.Split(new[] { delimiter }, StringSplitOptions.RemoveEmptyEntries);

                foreach (var s in sections)
                {
                    if (s.Contains("Content-Disposition"))
                    {
                        var nameMatch = new Regex(@"(?<=name\=\"")(.*?)(?=\"")").Match(s);
                        var name = nameMatch.Value.Trim().ToLower();

                        if (name == FilePartName && !string.IsNullOrEmpty(FilePartName))
                        {
                            var re = new Regex(@"(?<=Content\-Type:)(.*?)(?=\r\n\r\n)");
                            var contentTypeMatch = re.Match(content);

                            re = new Regex(@"(?<=filename\=\"")(.*?)(?=\"")");
                            var filenameMatch = re.Match(content);

                            if (contentTypeMatch.Success && filenameMatch.Success)
                            {
                                ContentType = contentTypeMatch.Value.Trim();
                                Filename = filenameMatch.Value.Trim();

                                var startIndex = contentTypeMatch.Index + contentTypeMatch.Length + "\r\n\r\n".Length;

                                var delimiterBytes = encoding.GetBytes("\r\n" + delimiter);
                                var endIndex = IndexOf(data, delimiterBytes, startIndex);

                                var contentLength = endIndex - startIndex;

                                var fileData = new byte[contentLength];

                                Buffer.BlockCopy(data, startIndex, fileData, 0, contentLength);

                                FileContents = fileData;
                            }
                        }
                        else if (!string.IsNullOrWhiteSpace(name))
                        {
                            var startIndex = nameMatch.Index + nameMatch.Length + "\r\n\r\n".Length;
                            Parameters.Add(name, s.Substring(startIndex).TrimEnd('\r', '\n').Trim());
                        }
                    }
                }

                if (FileContents != null || Parameters.Count != 0)
                {
                    Success = true;
                }
            }
        }

        public static int IndexOf(byte[] searchWithin, byte[] serachFor, int startIndex)
        {
            var index = 0;
            var startPos = Array.IndexOf(searchWithin, serachFor[0], startIndex);

            if (startPos != -1)
            {
                while (startPos + index < searchWithin.Length)
                {
                    if (searchWithin[startPos + index] == serachFor[index])
                    {
                        index++;
                        if (index == serachFor.Length)
                        {
                            return startPos;
                        }
                    }
                    else
                    {
                        startPos = Array.IndexOf(searchWithin, serachFor[0], startPos + index);
                        if (startPos == -1)
                        {
                            return -1;
                        }

                        index = 0;
                    }
                }
            }

            return -1;
        }

        public static byte[] ToByteArray(Stream stream)
        {
            var buffer = new byte[32768];
            using (var ms = new MemoryStream())
            {
                while (true)
                {
                    var read = stream.Read(buffer, 0, buffer.Length);
                    if (read <= 0)
                    {
                        return ms.ToArray();
                    }

                    ms.Write(buffer, 0, read);
                }
            }
        }
    }
}
